package com.freewater.javabase.demo.crypto;

import javax.crypto.KeyAgreement;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

/**
 * 密匙交换算法：DH算法
 * https://www.liaoxuefeng.com/wiki/1252599548343744/1304227905273889
 * https://www.zhihu.com/question/29383090/answer/1649199245
 */
public class KeyExchange {
    public static void main(String[] args) {
        Person bob = new Person("Bob");
        Person alice = new Person("Alice");

        bob.generateKeyPair();
        alice.generateKeyPair();

        bob.generateSecretKey(alice.publicKey.getEncoded());
        alice.generateSecretKey(bob.publicKey.getEncoded());

        bob.printKeys();
        alice.printKeys();
    }

    static class Person {

        public final String name;

        public PublicKey publicKey;

        private PrivateKey privateKey;

        private byte[] secretKey;

        public Person(String name) {
            this.name = name;
        }

        public void generateKeyPair() {
            try {
                KeyPairGenerator generator = KeyPairGenerator.getInstance("DH");
                generator.initialize(512);
                KeyPair keyPair = generator.generateKeyPair();
                this.publicKey = keyPair.getPublic();
                this.privateKey = keyPair.getPrivate();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }

        public void generateSecretKey(byte[] receivedPublicKeyBytes) {
            try {
                // 从byte[]中恢复收到的公私
                KeyFactory keyFactory = KeyFactory.getInstance("DH");
                PublicKey receivedPublicKey = keyFactory.generatePublic(new X509EncodedKeySpec(receivedPublicKeyBytes));
                // 根据收到的公私+自己的私匙生成本地密匙
                KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
                keyAgreement.init(this.privateKey);
                keyAgreement.doPhase(receivedPublicKey, true);
                this.secretKey = keyAgreement.generateSecret();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            }
        }

        public void printKeys() {
            System.out.printf("Name: %s\n", this.name);
            System.out.printf("Private key: %x\n", new BigInteger(1, this.privateKey.getEncoded()));
            System.out.printf("Public key: %x\n", new BigInteger(1, this.publicKey.getEncoded()));
            System.out.printf("Secret key: %x\n", new BigInteger(1, this.secretKey));
        }
    }
}
